#pragma once

#include <memory.h>
#include <iostream>

#include "Exception.h"

namespace HenkoCppLib
{
	template <class T>
	class Pointer;
}

template <class T>
void* operator new(size_t bytes, HenkoCppLib::Pointer<T>& pointer);

template <class T>
void* operator new[](size_t bytes, HenkoCppLib::Pointer<T>& pointer);

template <class T>
void operator delete(void* memory, HenkoCppLib::Pointer<T>& pointer);

template <class T>
void operator delete[](void* memory, HenkoCppLib::Pointer<T>& pointer);

namespace HenkoCppLib
{
	template <class T>
	class Pointer
	{
	private:
		struct Data
		{
			size_t refcount;
			size_t numbytes;
			size_t elemsize;
			size_t flags;
			T* data;

		public:
			Data(void* pointer, size_t bytes, bool isarray)
			{
				this->refcount = 1;
				this->numbytes = bytes;
				this->data = (T*)pointer;
				this->flags = (int)isarray;
				this->elemsize = sizeof(T);
			}

			~Data()
			{
				if (IsArray())
				{
					delete[] data;
				}
				else
				{
					delete data;
				}
			}

			inline int GetSize() const
			{
				return (int)(numbytes / elemsize);
			}

			inline T* GetElement(size_t index) const
			{
				return (T*)((char*)data + index * elemsize);
			}

			inline bool IsArray() const
			{
				return (flags & 1) > 0;
			}

			inline bool IsConfirmed() const
			{
				return (flags & 2) > 0;
			}

			void Confirm(T* memory)
			{
				//Compiler overallocation methods fix for operator new[] allocating array of objects.
				//It sometimes stores the number of elements in the first 4 bytes to be able to call the
				//correct number of destructors on delete.

				//VS: [4 bytes : Number of elements][n bytes : Real data block]
				//    |data pointer                 |memory pointer

				//No overallocation detected
				if (memory == data)
				{
					flags |= 2;
					return;
				}

				//Test overallocation method
				if ((void*)memory == (void*)((size_t*)data + 1))
				{
					numbytes -= (char*)memory - (char*)data;
					size_t* elemnumptr = (size_t*)data;
					if (*elemnumptr == (size_t)(numbytes / elemsize))
					{
						data = memory;
						flags |= 2;
						return;
					}
				}

				//Invalid pointer received
				throw InvalidPointerException();
			}
		};

	private:
		Data* data;
		int offset;

	private:
		void AddReference() const
		{
			if (data)
			{
				data->refcount++;
			}
		}

		void RemoveReference()
		{
			if (data)
			{
				data->refcount--;
				if (data->refcount <= 0)
				{
					delete data;
					data = NULL;
				}
			}
		}

	public:
		Pointer()
		{
			data = NULL;
			offset = 0;
		}

		Pointer(const Pointer& pointer)
		{
			if (pointer.IsInvalid())
			{
				throw InvalidPointerException();
			}
			data = pointer.data;
			offset = pointer.offset;
			pointer.AddReference();
		}

		Pointer(T* memory)
		{
			if (!data)
			{
				throw InvalidPointerException();
			}
			data->Confirm(memory);
			offset = 0;
		}

		template <class G> friend class Pointer;
		template <class G>
		Pointer(const Pointer<G>& pointer)
		{
			data = (Data*)pointer.data;
			offset = pointer.offset;
			pointer.AddReference();
		}

		~Pointer()
		{
			RemoveReference();
		}

	public:
		Pointer<T>& operator=(const Pointer<T>& other)
		{
			if (other.IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (this == &other)
			{
				return *this;
			}
			if (data)
			{
				RemoveReference();
			}
			data = other.data;
			if (data)
			{
				other.AddReference();
			}
			return *this;
		}

		Pointer<T>& operator=(T* memory)
		{
			if (!data)
			{
				throw InvalidPointerException();
			}
			data->Confirm(memory);
			offset = 0;
			return *this;
		}
		Pointer<T>& operator=(int value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (value >= 0 && value < data->GetSize())
			{
				offset = value;
			}
			else
			{
				throw OutOfBoundsException(value, 0, data->GetSize());
			}
			return *this;
		}

		bool operator<(int value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			return offset < value;
		}
		bool operator<=(int value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			return offset <= value;
		}
		bool operator>(int value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			return offset > value;
		}
		bool operator>=(int value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			return offset >= value;
		}

		Pointer<T>& operator+=(int value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (offset + value >= 0 && offset + value <= data->GetSize())
			{
				offset += value;
			}
			else
			{
				throw OutOfBoundsException(value, 0 - offset, data->GetSize() - offset - 1);
			}
			return *this;
		}

		Pointer<T>& operator-=(int value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (offset - value >= 0 && offset - value < data->GetSize())
			{
				offset -= value;
			}
			else
			{
				throw OutOfBoundsException(value, offset + data->GetSize() + 1, offset);
			}
			return *this;
		}

		Pointer<T>& operator++()
		{
			*this+=1;
			return *this;
		}
		Pointer<T>& operator--()
		{
			*this-=1;
			return *this;
		}
		Pointer<T> operator++(int)
		{
			Pointer<T> result = *this;
			*this+=1;
			return result;
		}
		Pointer<T> operator--(int)
		{
			Pointer<T> result = *this;
			*this-=1;
			return result;
		}
		Pointer<T> operator+(int value)
		{
			Pointer<T> result = *this;
			result+=value;
			return result;
		}

		Pointer<T> operator-(int value)
		{
			Pointer<T> result = *this;
			result-=value;
			return result;
		}

		T& operator[](int index)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (offset + index >= 0 && offset + index < data->GetSize())
			{
				return *(data->GetElement(offset + index));
			}
			else
			{
				throw OutOfBoundsException(index, 0 - offset, data->GetSize() - offset - 1);
			}
		}

		T& operator*()
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			return *(data->GetElement(offset));
		}

		T* operator->()
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			return data->GetElement(offset);
		}

		//Check if its valid
		operator bool()
		{
			return IsValid();
		}

		/** CONVERSION TO NATIVE POINTER IS UNSAFE! **/
		#ifdef _MSC_VER
			__declspec(deprecated("Converting a Smart Pointer to a native pointer may be unsafe! Please use it cautiously! Do NOT store this native pointer for long because the pointed memory will be released when all the Smart Pointers get destroyed resulting an invalid native pointer!"))
		#endif

		operator const T*()

		#ifndef _MSC_VER
			__attribute__ ((deprecated))
		#endif

		{
			return (const T*)data->GetElement(offset);
		}

		inline void ResetOffset()
		{
			offset = 0;
		}

	public:
		inline int GetSize() const
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (data)
			{
				return data->GetSize();
			}
			else
			{
				return 0;
			}
		}

		inline int GetOffset() const
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			return offset;
		}

		inline int GetNumBytes() const
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (data)
			{
				return data->numbytes;
			}
			else
			{
				return 0;
			}
		}

		inline int GetRefCount() const
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (data)
			{
				return data->refcount;
			}
			else
			{
				return 0;
			}
		}

		/// ********** WARNING! **********
		/// This may be unsafe! Use it cautiously!
		/// Do NOT store the returned native pointer for long because the pointed memory will be released when all the Smart Pointers get destroyed resulting an invalid native pointer! Native pointer references are NOT counted!
		const T* GetNativePointer() const
		{
			if (data)
			{
				return data->GetElement(offset);
			}
			else
			{
				return NULL;
			}
		}

		inline bool IsBeforeLast() const
		{
			return offset < GetSize() - 1;
		}
		inline bool IsAtLast() const
		{
			return offset == GetSize() - 1;
		}
		inline bool IsAtEnd() const
		{
			return offset == GetSize();
		}
		inline bool IsValidOffset() const
		{
			return offset >= 0 && offset < data->GetSize();
		}
		inline bool IsValid() const
		{
			return data != 0 && IsValidOffset() && data->IsConfirmed();
		}
		inline bool IsInvalid() const
		{
			return !IsValid();
		}
		inline bool IsAtFirst() const
		{
			return offset == 0;
		}
	public:
		void MemSet(unsigned char value)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			memset(data->GetElement(offset), value, data->numbytes - offset);
		}

		/*void MemSet(const T& value)
		{
			if (!data)
			{
				throw NullPointerException();
			}

			//TODO
		}*/

		void MemCopyFromNative(const T* from, int count, int tostartindex = 0)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (count > 0 && offset + tostartindex >= 0 && offset + tostartindex + count <= data->GetSize())
			{
				memmove(data->GetElement(offset + tostartindex), from, count * sizeof(T));
			}
			else
			{
				throw InvalidValuesException();
			}
		}

		void MemCopyFrom(Pointer<T> from, int count, int tostartindex = 0)
		{
			MemCopyFromNative(from.GetNativePointer(), count, tostartindex);
		}

		bool MemCompareNative(T* other, int count)
		{
			if (IsInvalid())
			{
				throw InvalidPointerException();
			}
			if (count > 0 && offset + count <= data->GetSize())
			{
				return 0 == memcmp(data->GetElement(offset), other, count * sizeof(T));
			}
			else
			{
				throw OutOfBoundsException(count, 1, data->GetSize() - offset);
			}
		}

		bool MemCompare(Pointer<T> other, int count)
		{
			return MemCompareNative(other->GetNativePointer(), count);
		}



	private:
		void New(void* memory, size_t bytes, bool isarray)
		{
			data = new Data(memory, bytes, isarray);
		}

	public:
		friend void* (::operator new<>(size_t bytes, Pointer<T>& pointer));
		friend void* (::operator new[]<>(size_t bytes, Pointer<T>& pointer));
		friend void (::operator delete<>(void* memory, HenkoCppLib::Pointer<T>& pointer));
		friend void (::operator delete[]<>(void* memory, HenkoCppLib::Pointer<T>& pointer));
	};

	#define IntPtr HenkoCppLib::Pointer<int>
	#define CharPtr HenkoCppLib::Pointer<char>
	#define ShortPtr HenkoCppLib::Pointer<short>
	#define FloatPtr HenkoCppLib::Pointer<float>
	#define DoublePtr HenkoCppLib::Pointer<double>

	#define UIntPtr HenkoCppLib::Pointer<unsigned int>
	#define UCharPtr HenkoCppLib::Pointer<unsigned char>
	#define UShortPtr HenkoCppLib::Pointer<unsigned short>

	#define IntPtr2D HenkoCppLib::Pointer<Pointer<int> >
	#define CharPtr2D HenkoCppLib::Pointer<Pointer<char> >
	#define ShortPtr2D HenkoCppLib::Pointer<Pointer<short> >
	#define FloatPtr2D HenkoCppLib::Pointer<Pointer<float> >
	#define DoublePtr2D HenkoCppLib::Pointer<Pointer<double> >

	#define UIntPtr2D HenkoCppLib::Pointer<Pointer<unsigned int> >
	#define UCharPtr2D HenkoCppLib::Pointer<Pointer<unsigned char> >
	#define UShortPtr2D HenkoCppLib::Pointer<Pointer<unsigned short> >

	#define IntPtr3D HenkoCppLib::Pointer<Pointer<Pointer<int> > >
	#define CharPtr3D HenkoCppLib::Pointer<Pointer<Pointer<char> > >
	#define ShortPtr3D HenkoCppLib::Pointer<Pointer<Pointer<short> > >
	#define FloatPtr3D HenkoCppLib::Pointer<Pointer<Pointer<float> > >
	#define DoublePtr3D HenkoCppLib::Pointer<Pointer<Pointer<double> > >

	#define UIntPtr3D HenkoCppLib::Pointer<Pointer<Pointer<unsigned int> > >
	#define UCharPtr3D HenkoCppLib::Pointer<Pointer<Pointer<unsigned char> > >
	#define UShortPtr3D HenkoCppLib::Pointer<Pointer<Pointer<unsigned short> > >

}

std::ostream& operator<<(std::ostream& stream, HenkoCppLib::Pointer<char>& str);

template <class T>
void* operator new(size_t bytes, HenkoCppLib::Pointer<T>& pointer)
{
	void* memory = operator new(bytes);
	pointer.New(memory, bytes, false);
	return memory;
}

template <class T>
void* operator new[](size_t bytes, HenkoCppLib::Pointer<T>& pointer)
{
	void* memory = operator new(bytes);
	pointer.New(memory, bytes, true);
	return memory;
}

template <class T>
void operator delete(void* memory, HenkoCppLib::Pointer<T>& pointer)
{
	operator delete(memory);
}

template <class T>
void operator delete[](void* memory, HenkoCppLib::Pointer<T>& pointer)
{
	operator delete(memory);
}
